/*************************************************
 * Grid.java                                     *
 * A JPanel which draws and manages the graphical*
 * grid component of CRIEP.                      *
 *************************************************/
package graphics;

//Imports
import robot.Robot;
import arena.Arena;
import arena.Node;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.Iterator;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JPanel;

/**
 * A JPanel which acts as the graphics container for the grid.
 *
 * @author      Gabriel Loewen
 * @author      Jimmy Weston
 */
public class Grid extends JPanel {

    //Define an the arena which owns this grid
    private Arena arena;
    //Iterators for traversing the node and robot hashmaps
    private Iterator nodeIterator;
    private Iterator robotIterator;
    //Variables for graphics centering and zooming
    private int min = 0;
    private int centerY = 0;
    private int centerX = 0;
    public int squareSize = 0;
    private int zoom = 0;
    //Define points which track mouse movements and dragging
    private Point mousePoint = new Point();
    private Point dragDifference = new Point();
    private Point oldDifference = new Point();
    private Point dragPoint = new Point();
    private Color LTYELLOW = new Color(0x3F4C6B);
    private Point clickPoint = new Point();

    /**
     * Constructor which takes its owner as input
     * @param a     The arena that owns this grid
     */
    public Grid(Arena a) {
        this.arena = a;
        this.setBorder(BorderFactory.createEtchedBorder());

        //Add listener to track zooming using the mouse wheel
        this.addMouseWheelListener(new MouseWheelListener() {

            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                //Get number of rotations of the mouse wheel
                int notches = e.getWheelRotation();

                if (zoom >= 0) {
                    //Zoom by a factor of 2*notches
                    zoom -= notches * 2;
                    if (dragDifference.x > (getWidth() - (squareSize * arena.width)) / 2 + squareSize * arena.width - (squareSize / 3)) {
                        dragDifference.x = (getWidth() - (squareSize * arena.width)) / 2 + squareSize * arena.width - (squareSize / 3);
                    } else if (dragDifference.x + getWidth() - (getWidth() - (squareSize * arena.width)) / 2 - (squareSize / 3) < 0) {
                        dragDifference.x = (getWidth() - (squareSize * arena.width)) / 2 - getWidth() + (squareSize / 3);
                    }
                    if (dragDifference.y + (getHeight() - (squareSize * arena.height)) / 2 > getHeight() - (squareSize / 3)) {
                        dragDifference.y = getHeight() - (squareSize / 3) - (getHeight() - (squareSize * arena.height)) / 2;
                    } else if (dragDifference.y + getHeight() < (getHeight() - (squareSize * arena.height)) / 2 + (squareSize / 3)) {
                        dragDifference.y = (getHeight() - (squareSize * arena.height)) / 2 + (squareSize / 3) - getHeight();
                    }
                }
                //Make sure it doesn't zoom out past the original size
                if (zoom < 0) {
                    zoom = 0;
                    dragDifference.x = 0;
                    dragDifference.y = 0;
                    oldDifference.x = 0;
                    oldDifference.y = 0;
                }
                //Repaint at every zoom
                repaint();
            }
        });

        //Add a listener to keep track of mouse clicks and drags
        this.addMouseListener(new MouseListener() {

            public void mousePressed(MouseEvent evt) {
                //Only drag if the grid is zoomed in
                if (zoom != 0) {
                    setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
                    dragPoint.x = mousePoint.x;
                    dragPoint.y = mousePoint.y;
                }

                //Get the graphics component
                Graphics2D g = (Graphics2D) getGraphics();

                //Draw a coordinate of the selected node in (x,y) format
                for (int i = 0; i < arena.width; i++) {
                    for (int j = 0; j < arena.height; j++) {
                        if (mousePoint.x >= (centerX + i * squareSize)
                                && mousePoint.x <= (squareSize + centerX + i * squareSize)
                                && mousePoint.y >= (centerY + (arena.height - j - 1) * squareSize)
                                && mousePoint.y <= (squareSize + centerY + (arena.height - j - 1) * squareSize)) {
                            clickPoint = new Point(i, j);
                            g.setColor(Color.WHITE);
                            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f));
                            g.fillRect((centerX + i * squareSize), (centerY + (arena.height - j - 1) * squareSize), squareSize, squareSize);
                            String position = "Pos: " + i + "," + j;
                            String numVisits = "Freq: " + arena.nodeMap.get(i + "," + j).getNumVisits();
                            int max = Math.max(g.getFontMetrics().stringWidth(position), g.getFontMetrics().stringWidth(numVisits));
                            g.fillRect(5, 5, max + 20, g.getFontMetrics().getHeight() * 2 + 12);
                            g.setColor(Color.BLACK);
                            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
                            g.drawRect((centerX + i * squareSize), (centerY + (arena.height - j - 1) * squareSize), squareSize, squareSize);
                            g.drawString(position, 10, 20);
                            g.drawString(numVisits, 10, 20 + g.getFontMetrics().getHeight() + 2);
                            g.drawRect(5, 5, max + 20, g.getFontMetrics().getHeight() * 2 + 12);
                        }
                    }
                }
            }

            public void mouseReleased(MouseEvent evt) {
                //In order to drag we need to maintaint two reference points,
                //the initial click point and the previous point.
                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                if (zoom != 0) {
                    oldDifference.x = dragDifference.x;
                    oldDifference.y = dragDifference.y;
                }
                clickPoint = null;
                repaint();

            }

            public void mouseEntered(MouseEvent arg0) {
            }

            public void mouseExited(MouseEvent arg0) {
            }

            public void mouseClicked(MouseEvent arg0) {
            }
        });

        this.addMouseMotionListener(new MouseMotionListener() {

            public void mouseMoved(MouseEvent e) {
                //Update the mouse position when it is moved
                mousePoint.x = e.getX();
                mousePoint.y = e.getY();
            }

            public void mouseDragged(MouseEvent e) {
                mousePoint.x = e.getX();
                mousePoint.y = e.getY();
                //If the grid is zoomed in then update the drag position
                if (zoom != 0) {
                    dragDifference.x = oldDifference.x + (dragPoint.x - mousePoint.x);
                    dragDifference.y = oldDifference.y + (dragPoint.y - mousePoint.y);
                    if (dragDifference.x > (getWidth() - (squareSize * arena.width)) / 2 + squareSize * arena.width - (squareSize / 3)) {
                        dragDifference.x = (getWidth() - (squareSize * arena.width)) / 2 + squareSize * arena.width - (squareSize / 3);
                    } else if (dragDifference.x + getWidth() - (getWidth() - (squareSize * arena.width)) / 2 - (squareSize / 3) < 0) {
                        dragDifference.x = (getWidth() - (squareSize * arena.width)) / 2 - getWidth() + (squareSize / 3);
                    }
                    if (dragDifference.y + (getHeight() - (squareSize * arena.height)) / 2 > getHeight() - (squareSize / 3)) {
                        dragDifference.y = getHeight() - (squareSize / 3) - (getHeight() - (squareSize * arena.height)) / 2;
                    } else if (dragDifference.y + getHeight() < (getHeight() - (squareSize * arena.height)) / 2 + (squareSize / 3)) {
                        dragDifference.y = (getHeight() - (squareSize * arena.height)) / 2 + (squareSize / 3) - getHeight();
                    }
                    repaint();
                    //Get the graphics component
                    Graphics2D g = (Graphics2D) getGraphics();
                    if (clickPoint != null) {
                        //Draw a coordinate of the selected node in (x,y) format
                        g.setColor(Color.WHITE);
                        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f));
                        g.fillRect((centerX + clickPoint.x * squareSize), (centerY + (arena.height - clickPoint.y - 1) * squareSize), squareSize, squareSize);
                        String position = "Pos: " + clickPoint.x + "," + clickPoint.y;
                        String numVisits = "Freq: " + arena.nodeMap.get(clickPoint.x + "," + clickPoint.y).getNumVisits();
                        int max = Math.max(g.getFontMetrics().stringWidth(position), g.getFontMetrics().stringWidth(numVisits));
                        g.fillRect(5, 5, max + 20, g.getFontMetrics().getHeight() * 2 + 12);
                        g.setColor(Color.BLACK);
                        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
                        g.drawRect((centerX + clickPoint.x * squareSize), (centerY + (arena.height - clickPoint.y - 1) * squareSize), squareSize, squareSize);
                        g.drawString(position, 10, 20);
                        g.drawString(numVisits, 10, 20 + g.getFontMetrics().getHeight() + 2);
                        g.drawRect(5, 5, max + 20, g.getFontMetrics().getHeight() * 2 + 12);
                    }
                }
            }
        });

    }

    /**
     * Redraws a specific area of the grid where it is known that a robot is
     * moving.  This is done for efficiency purposes because if we know that
     * only the specified region of the grid is changing it is unnecessary to
     * repaint the entire component.  In order to do this both the old and
     * new location of the robot must be repainted.
     *
     * @param oldPnt    The old location of the robot
     * @param newPnt    The new location of the robot
     */
    public void redrawRobot(Point oldPnt, Point newPnt) {
        //Redraw an individual robot from its old position to its new position
        //as it is moving
        Rectangle oldRect = new Rectangle();
        Rectangle newRect = new Rectangle();
        oldRect.setBounds(centerX + (oldPnt.x - 1) * squareSize, centerY + (arena.height - (oldPnt.y + 1) - 1) * squareSize, squareSize * 3, squareSize * 3);
        newRect.setBounds(centerX + (newPnt.x - 1) * squareSize, centerY + (arena.height - (newPnt.y + 1) - 1) * squareSize, squareSize * 3, squareSize * 3);
        repaint(oldRect);
        repaint(newRect);
    }

    /**
     * Paints the grid onto the component.  Calculates the size of the square
     * depending on the size of the component and creates the appropriate spacing
     * necessary.  Also resizes the square size depending on the level of zoom.
     *
     * @param graphics  The default graphics component
     */
    @Override
    public void paintComponent(Graphics graphics) {

        Graphics2D g = (Graphics2D) graphics;
        Boolean antialiasing = (Boolean) gui.Client.tblGridOptions.getModel().getValueAt(1, 1);
        if (antialiasing != null && antialiasing == true) {
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        }

        Boolean dithering = (Boolean) gui.Client.tblGridOptions.getModel().getValueAt(2, 1);
        if (dithering != null && dithering == true) {
            g.setRenderingHint(RenderingHints.KEY_DITHERING,
                    RenderingHints.VALUE_DITHER_ENABLE);
        }

        g.setBackground(Node.UNKNOWN);

        //Determine the best size for each node
        min = Math.min(this.getWidth(), this.getHeight());
        if (min == this.getWidth() && !(arena.height > arena.width)) {
            squareSize = (this.getWidth() / arena.width);
        } else if (min == this.getHeight() && !(arena.width > arena.height)) {
            squareSize = (this.getHeight() / arena.height);
        } else if (min == this.getWidth() && (arena.height > arena.width)) {
            squareSize = (this.getHeight() / arena.height);
        } else if (min == this.getHeight() && (arena.width > arena.height)) {
            squareSize = (this.getWidth() / arena.width);
        } else {
            squareSize = (((this.getWidth() / arena.width) + (this.getHeight() / arena.height)) / 2);
        }

//Increase the node size according to the zoom
        squareSize += zoom;

        //Ensure that the grid is always centered on the panel
        centerY =
                this.getHeight() / 2 - ((squareSize * arena.height) / 2) - dragDifference.y;
        centerX =
                this.getWidth() / 2 - ((squareSize * arena.width) / 2) - dragDifference.x;

        //Always clear the screen before painting something new
        g.clearRect(0, 0, getWidth(), getHeight());

        //Iterate through the nodes and draw them in the correct locations
        nodeIterator = arena.nodeMap.values().iterator();
        while (nodeIterator.hasNext()) {
            Node node = (Node) nodeIterator.next();
            Point location = node.getLocation();
            Color nColor = node.getColor();
            g.setColor(nColor);
            g.fillRect(location.x * squareSize + centerX,
                    (arena.height - location.y - 1) * squareSize + centerY, squareSize, squareSize);
            if (nColor == Node.DELETED) {
                g.setColor(Color.BLACK);
                g.drawLine(location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY, location.x * squareSize + centerX + squareSize - 1, (arena.height - location.y - 1) * squareSize + centerY + squareSize - 1);
                g.drawLine(location.x * squareSize + centerX + squareSize - 1, (arena.height - location.y - 1) * squareSize + centerY, location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY + squareSize - 1);
            }

            if (nColor != Node.DELETED) {
                g.setColor(LTYELLOW);
                g.fillOval(location.x * squareSize + centerX + squareSize / 2 - (squareSize / 5) / 2, (arena.height - location.y - 1) * squareSize + centerY + squareSize / 2 - (squareSize / 5) / 2, (squareSize / 5), (squareSize / 5));
                Boolean highlighting = (Boolean) gui.Client.tblGridOptions.getModel().getValueAt(0, 1);
                if (highlighting != null && highlighting == true) {
                    g.setColor(Color.LIGHT_GRAY);
                    g.drawRect(location.x * squareSize + centerX,
                            (arena.height - location.y - 1) * squareSize + centerY, squareSize, squareSize);
                }
                List<Node> adjNodes = node.getAdjNodes();
                for (int i = 0; i < adjNodes.size(); i++) {
                    Node n = adjNodes.get(i);
                    if (n != null && node.getColor() != Node.DELETED && n.getColor() != Node.DELETED) {
                        if (n.getLocation().x < location.x) {
                            g.drawLine(location.x * squareSize + centerX + squareSize / 2, (arena.height - location.y - 1) * squareSize + centerY + squareSize / 2, location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY + squareSize / 2);
                        } else if (n.getLocation().x > location.x) {
                            g.drawLine(location.x * squareSize + centerX + squareSize / 2, (arena.height - location.y - 1) * squareSize + centerY + squareSize / 2, location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY + squareSize / 2);
                        } else if (n.getLocation().y > location.y) {
                            g.drawLine(location.x * squareSize + centerX + squareSize / 2, (arena.height - location.y - 1) * squareSize + centerY + squareSize / 2, location.x * squareSize + centerX + squareSize / 2, (arena.height - location.y - 1) * squareSize + centerY);
                        } else {
                            g.drawLine(location.x * squareSize + centerX + squareSize / 2, (arena.height - location.y - 1) * squareSize + centerY + squareSize / 2, location.x * squareSize + centerX + squareSize / 2, (arena.height - location.y - 1) * squareSize + centerY + squareSize);
                        }
                    }
                }
            }
            
            /*
            g.setColor(Color.BLACK);
            if (location.x == 0 && location.y == 0) {
                g.drawLine(location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY + squareSize, location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY + squareSize);
                g.drawLine(location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY + squareSize, location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY);
            } else if (location.x == arena.width - 1 && location.y == 0) {
                g.drawLine(location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY + squareSize, location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY + squareSize);
                g.drawLine(location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY + squareSize, location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY);
            } else if (location.x == 0 && location.y == arena.height - 1) {
                g.drawLine(location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY, location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY);
                g.drawLine(location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY, location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY + squareSize);
            } else if (location.x == arena.width - 1 && location.y == arena.height - 1) {
                g.drawLine(location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY, location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY);
                g.drawLine(location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY, location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY + squareSize);
            } else if (location.x == 0) {
                g.drawLine(location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY + squareSize, location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY);
            } else if (location.x == arena.width - 1) {
                g.drawLine(location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY + squareSize, location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY);
            } else if (location.y == 0) {
                g.drawLine(location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY + squareSize, location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY + squareSize);
            } else if (location.y == arena.height - 1) {
                g.drawLine(location.x * squareSize + centerX, (arena.height - location.y - 1) * squareSize + centerY, location.x * squareSize + centerX + squareSize, (arena.height - location.y - 1) * squareSize + centerY);
            }*/
        }


        //Iterate through the robots and draw them within the correct nodes
        robotIterator = arena.robotMap.values().iterator();
        while (robotIterator.hasNext()) {
            Robot robot = (Robot) robotIterator.next();
            Arrow.draw(g, robot.getLocation().x * squareSize + centerX,
                    (arena.height - robot.getLocation().y - 1) * squareSize + centerY, squareSize,
                    robot.getHeading(), robot.getID());

        }
    }
}
